简介
在Android开发中,经常会遇到需要在不同线程之间切换的需要,比如网络请求(Android为了防止出现ANR异常,所以规定在Main Thread中不能进行网络请求,且最好不要进行耗时操作),子线程通信等等。此外,Android在设计之初,为了安全和用户体验考虑,规定了只允许在Main Thread里面进行UI更新,而不能在子线程里面进行,否则会抛出异常。这个时候,就需要用到Android的消息传递机制 — Handler。
在Android中,Handler消息传递机制主要依赖于Handler,Message,MessageQueue,Looper。其中:
- Handler负责消息的发送与接收处理。
- Message负责消息的封装,他本身可以看做消息的载体。
- MessageQueue:是一个消息队列,所有需要发送的消息用类似于链表的形式进行存储,并且依据于消息消费的时间为标志确定存储位置。
- Looper:进行消息循环与消息分发。
简单使用如下:
1 | Handler handler = new Handler() { |
在使用Handler时,创建一个Handler并重写其handleMessage方法,参数Message中包含了传递的信息,信息来源等等,可以根据msg.what判断消息来源并做相应处理。但不建议这样直接创建,如果这样在Activity中创建的话,当activity被finish之后,可能有消息还在继续发送,而此时的message中保留有activity中handler的引用,而这个handler里面有这个被finish的activity的隐式引用,导致此activity无法被销毁,这样就会有内存泄漏的风险。
解决办法一般是不让Handler内部类不持有外部activity的强引用,如下所示:
1 | private static class MyHandler extends Handler { |
把Handler设置为静态类,静态类不持有外部类的对象,所以activity销毁的时候不会受到Handler的限制,并且在Handler里面用到activity是持有的弱引用,不影响外部activity的销毁。
消息发送
上面是Handler接收到消息之后的最终处理,接下来看看消息发送的情况。Handler有多个方法用来应对不同情景的数据发送,以下为几个例子:
1.sendEmptyMessageDelayed(int what, long delayMillis),带有消息来源的延时消息发送。
1 | public final boolean sendEmptyMessageDelayed(int what, long delayMillis) { |
2.sendEmptyMessage(int what) 只包含来源身份信息的空消息发送
1 | public final boolean sendEmptyMessage(int what) |
3.sendEmptyMessageDelayed(int what, long delayMillis) 延时空消息发送
1 | public final boolean sendEmptyMessageDelayed(int what, long delayMillis) { |
4.sendMessageAtFrontOfQueue(Message msg)把消息放入队列头部,其实实现是把延时发送消息设置为0。
1 | public final boolean sendMessageAtFrontOfQueue(Message msg) { |
等等……
可以看到,所有方法都调用到了sendMessageAtTime方法,只是参数不同而已。
1 | public boolean sendMessageAtTime(Message msg, long uptimeMillis) { |
方法参数Message信息与消息消费时间。判断了是否存在MessageQueue,为空则会报错,存在则把消息根据时间放入queue中,再看看这个MessageQueue是什么时候初始化的。
1 | public Handler(Callback callback, boolean async) { |
通过跟踪我们可以发现,用来存储消息的MessageQueue是在Handler初始化的时候初始化的,mQueue = mLooper.mQueue; 且这个mLooper也是这个时候通过Looper.myLooper();初始化的,我们现在去Looper里面看看这个初始化过程:
1 | public static @Nullable Looper myLooper() { |
可以发现,是从一个ThreadLocal里面拿到的Looper对象,继续查看是在哪里进行Looper对象的设置的。
1 | private static void prepare(boolean quitAllowed) { |
通过跟踪观察可以发现,Looper是在通过Looper.prepare()方法把Looper对象放入ThreadLocal对象里面的,并且在此时创建了MessageQueue对象。所以当我们在子线程使用Handler的时候,在创建Handler前必须要先调用Looper.prepare()方法的原因了,因为在创建Handler的时候会进行Looper的初始化。如果没有先调用Looper.prepare()的话,sThreadLocal.get()的值就为空,就会抛出RuntimeException。此外有个特例就是,如果我们在Main Thread里面使用Handler的话则不需要使用Looper.prepare()就可以直接创建Handler使用了,其实这是因为在Android程序创建的时候已经调用过这个方法了。Android程序刚开始的入口是ActivityThread的main方法,在里面可以看到相关的设定:
1 | public static void main(String[] args) { |
我们可以看到,在ActivityThread的main方法里面,调用了Looper.prepareMainLooper();方法创建Looper对象,并且最后还调用了Looper.loop(); 方法进行消息循环,这也是在Main Thread里面调用为何不需要提前用Looper.prepare()就可以直接使用的原因了。我们再看看Looper.prepareMainLooper()是怎么创建Looper对象的:
1 | public static void prepareMainLooper() { |
在prepareMainLooper方法里面调用了prepare方法,并且Main Thread里面和其他子线程里面都保证了只能创建一次Looper对象,即一个线程只能有一个Looper对象,否则会抛出IllegalStateException的异常。
到此对Handler,Looper的创建做一个总结:
- 在子线程创建使用Handler的时候必须先调用
Looper.prepare()方法创建Looper,Looper里面创建的对象由一个ThreadLocal对象进行存储,主线程创建使用Handler的时候不需要提前调用Looper.prepare()方法,因为在程序开始的时候在ActivityThread的main方法里面已经调用过Looper.prepareMainThread()方法了。每个线程里面只能调用Looper.prepare()方法一次,只能创建一个Looper对象,否则会抛出异常。 - Handler创建的时候,会调用Looper的方法去实例化Looper和MessageQueue对象。
消息存储与消息循环
几乎所有的发送消息的方法最后都调用了sendMessageAtTime方法,在这个发放中判断了是否存在MessageQueue对象之后调用了enqueueMessage方法,在这个方法中,把msg.targt设置为此Handler对象,注意,这里是用于之后在MessageQueue里面消费消息时找到对应的处理此消息的Handler,然后调用MessageQueue的enqueueMessage()方法,此方法主要是用于把消息放入MessageQueue里面,即入队操作。
1 | boolean enqueueMessage(Message msg, long when) { |
Message消息通过MessageQueue的enqueueMessage方法进行入队操作,在此方法中先判断msg.target是否存在,当前消息是否在使用中,是否收到退出信息等的检测,全部正常才进行入队,从上面代码我们可以看到,MessageQueue是通过类似于链表的形式存储Message消息的,且是通过消费时间进行排序的,通过前面我们可以知道,入队用于排序的时间是Message的消费时间,即System.currentTime()+delayMillis;循环遍历消息队列,找到msg.when应该插入的地方,插入链表,完成消息的入队操作。这里还需要注意的是nativeWake(mPtr);方法,这个方法是用通过JNI实现的,即在底层通过C实现的,然后通过JNI调用,在底层中维持了一个mWakeEventFd文件,这个文件是专门用于进程和线程之间通信的,并且通过epoll监控。这里在入队完成之后会调用此方法,通过此方法会向MwakeEventFD文件写入一个uint64_t,这个是用于唤醒消息循环线程的,在后面消息取出的时候进行详细说明。
通过上面的方法就完成了消息发送过程,接下来则是消息循环过程,当消息的消费时间到了之后取出相应的消息进行消费。通过上面的时候我们知道在线程中使用完Handler之后需要调用Looper.loop()完成整个过程,在主线程不需要我们操作是因为ActivityThread里面已经调用过了,在loop()里面做的就是消息的阻塞等待。
1 | public static void loop() { |
- 从代码中我们可以看出,这是个无限循环方法等待获取可以处理的消息,在循环里面,通过
do-while找到一个不为空且是asynchronous的消息,找到之后检测这个消息是否到了执行时间,如果不到的话,通过Math.min(msg.when - now, Integer.MAX_VALUE);设置等待时间,如果这个消息找到了,且到了执行时间了,那么就取出此消息,并把链表中此消息删除,把这个消息通过msg.markInUse();标志为使用中,把这个找到的消息返回给Looper的loop方法,进行消息分发。如果到了链表尾部还是没有符合的消息,则进入阻塞等待过程。 - 另外在此方法中还得注意
nativePollOnce(ptr, nextPollTimeoutMillis);这个方法也是JNI调用C++实现的。之前说到在通过Handler发送消息的时候,会向mWakeEventFd文件写一个uint_64,而nativePollOnce方法则阻塞监听mWakeEventFd文件以及唤醒消息循环线程的。当有消息发送的时候就会写入一个uint_64,而nativePollOnce里面则是一直监听这个文件,当有写入操作发生时,就会唤醒epoll_wait即消息循环线程,线程就会去取出队列里面的消息去执行操作,当队列里面没有消息的时候,又会继续等待,当下次有信息写入的时候则再次唤醒线程去取出队列消息。这样就完成一次消息循环。 - 继续看上面的取出消息之后的处理,上面说到取出消息之后loop()方法是调用了msg.target.dispatchMessage方法进行消息处理,而msg.target是之前设置的目标Handler,现在去Handler里面看看dispatchMessage的处理:
1 | public void dispatchMessage(Message msg) { |
这里就是如何处理信息了,如果为Message设置了callback的话则直接message.callback.run();处理消息,如果有设置Handler的callback,也进行分发;如果都没有的话,那就直接调用handleMessage(msg);还记得我们最开是的时候写的简单使用Handler的例子么,里面重写了一个方法,就是handleMessage(msg);所以到这里就清楚了,前面发出的消息就是在这里进行处理的。
另外还有很多经常用的方法都是通过包装的Handler来进行的,比如:
Activity.runOnUiThread(Runnable action)1
2
3
4
5
6
7public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
这个是Activity的方法,用于在切换到主线程运行,可以看到内部实现是判断当前线程是否是主线程,如果是的话直接运行,不是的话用mHandler.post发送出去,而mHandler是申明在主线程的Handler,经过发送之后再主线程的Handler里面完成调用。
View.post()
1 | public boolean post(Runnable action) { |
View.post()用于异步更新view,也是找到主线程Handler,然后通过Handler发送消息,在主线程更新Handler。另外还有很多经典方法都是用Handler更新数据的,最典型的是Android官方提供的异步工具AsyncTask,用它可以进行异步请求或者异步下载数据,下载图片等等,AsyncTask就是用Handler实现的多线程异步工具,开启子线程进行数据请求,然后通过Handler发送数据消息,最好在主线程里面处理收到的消息,完成异步请求。
###总结
在使用Handler的时候,主线程不需要调用
Looper.prepare()创建Looper,因为在程序开始的时候在ActivityTask里面的main方法里面调用了Looper.prepareMainLooper()创建了Looper对象,子线程中需要先调用Looper.prepare()创建Looper对象。每个线程只能有一个Looper对象。在创建Looper的时候会初始化MessageQueue对象,因为在创建Handler的时候需要是用到Looper对象以及MessageQueue对象,并且在Looper中保存示例是放在ThreadLocal里面的。初始完成之后用Handler发送信息,包括信息数据Message以及delayMillis,之后调用MessageQueue的
enqueueMessage()把消息按类似于链表结构并按照时间顺序排列,此时还会调用nativeWake(mPtr)方法向底层的mWakeEventFd文件写入uint64_t,唤醒消息循环线程。子线程发送完数据之后调用Looper.loop(),死循环等待到时间处理的消息出队,Looper取到消息之后,就进行消息分发,根据设置的callback进行处理消息,如果没有设置则调用Handler的handleMessage处理消息。MessageQueue的next方法负责进行消息出队操作,无限循环检查是否有到时间的消息,如果有则把他出队,如果没有则循环阻塞。出队消息然后交给Looper的loop方法进行消息分发。在消息出队的时候会调用
nativePollOnce(ptr, nextPollTimeoutMillis)方法,这个方法是用来通过epoll监听mWakeEventFd文件的,当监听到有uint64写入文件的时候唤醒消息循环线程取出消息并处理,没有消息的时候沉睡等待下次uint64的写入。这样就完成了一次消息的收发。
